package com.hq.security;

import java.util.concurrent.Callable;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 配合 TicketThreadSecurityDemo 做超卖票现象测试。
 * <p>
 * 总结lock 和 synchronized:
 * 1、lock是个类   synchronized 是java关键字，实现的过程利用java对象头 在jvm中实现同步
 * 2、lock能获取锁的状态，synchronized不能获取的任何信息
 * 3、lock   可中断、支持公平和非公平、可重入锁（拿到锁后可以再次请求锁）
 * synchronized 不可中断、非公平、可重入锁。
 * 4、lock需要在finally 中调用unlock方法 释放锁，不释放会死锁。 synchronized 会自动释放锁
 * 5、lock控制相对细，可以在指定的地方加锁 代码多的时候可操作性强 synchronized 只能锁死整个方法
 * 6、线程 1 2争取一个资源，1先拿到，在1被阻塞后。
 * 通过lock方法的 线程2不一定会一直等待    synchronized的线程2会一直等待
 * <p>
 * 疑问;为什么会有ReentrantLock 用synchronized不就行了？
 * 因为 synchronized 的执行会通过C语言调用操作系统Mutex，在后续进行了优化 有了锁升级的过程  从可重入-轻量级-重量级锁。
 * ReentrantLock 通过自旋 后续加入了锁升级   如果线程的执行过程过长 大量的自旋锁占用过大的cpu不适合
 * 只适合线程执行短的情况   可以打断其他的锁。有超时机制
 *
 * @author hq
 */
public class TicketSecutityCallable implements Callable<String> {
    /**
     * 票的数量
     */
    static int ticket = 1000;

    /** 线程不安全 会产生超卖现象  */
    //@Override
    //public String call() throws Exception {
    //    int sum = 0;
    //    while (true){
    //        if(ticket>0){
    //            //模拟卖票操作 每次票数减一
    //            Thread.sleep(1);
    //            ticket--;
    //            sum++;
    //        }else {
    //            return Thread.currentThread().getName()+"卖出："+sum;
    //        }
    //    }
    //}

    /**
     * 定义一个锁对象 只有拿到这个锁对象才能操作 锁对象需要实例共享的静态成员
     */
    private static Object object = new Object();
    /** 同步代码块的方式 保证线程安全 */
    //@Override
    //public String call() throws Exception {
    //    int sum = 0;
    //    while (true){
    //        synchronized (object){
    //            if(ticket>0){
    //                //模拟卖票操作 每次票数减一
    //                Thread.sleep(1);
    //                ticket--;
    //                sum++;
    //            }else {
    //                return Thread.currentThread().getName() + "卖出：" + sum;
    //            }
    //        }
    //    }
    //}

    /** 同步方法 实现线程安全 */
    //@Override
    //public String call() throws Exception {
    //    int sum = 0;
    //    while (true){
    //        if (removeTicket()){
    //            return Thread.currentThread().getName()+"卖出去票"+sum;
    //        }
    //        sum++;
    //    }
    //}

    /**
     * 卖一张票  synchronized作用在static方法上 就是当前类的类对象synchronized(TicketSecutityCallable.class)，
     * 作用在成员方法上就是synchronized(new TicketSecutityCallable()) 的对象
     */
    public synchronized static Boolean removeTicket() throws InterruptedException {
        if (ticket > 0) {
            //模拟卖票操作 每次票数减一
            Thread.sleep(1);
            ticket--;
        } else {
            return true;
        }
        return false;
    }


    /**
     * 通过Lock实现线程安全   ReentrantLock (约嗯锤 辣可)--可重入锁
     * getHoldCount()   查询当前线程对此锁的暂停数量。
     * tryLock()  只有在调用时它不被另一个线程占用才能获取锁。
     * new ReentrantLock(true); 公平锁， tryLock() 会导致公平锁变的不公平
     * <p>
     * ReentrantLock公平锁 FairSync （非而）
     * 1、进入lock方法后  进入acquire方法 直接跳转到tryAcquire方法 判断getState() 值是不是0
     * 2、如果是0  （资源竞争激烈和sync 重量级，资源竞争不太激烈 轻量级）
     * 2.1、判断队列有没有初始化 没有初始化直接拿到锁
     * 2.2、队列有初始化 看有没有数据 有就创建一个null的node对象把自己排在它后面，入队 入队后会再次自旋看，上个队列的头部在不在第一位
     * 2.3、在的话尝试拿锁 不在的话就park自己（修改操作系统了，锁就重量级了）
     * 3、如果1 判断当前线程是不是重入 是的话把状态改为2 不是的话重复2.1 2.2 2.3
     * <p>
     * ReentrantLock非公平锁 NonFairSync
     * 1、进入lock方法后  调用compareAndSetState方法直接尝试进行加锁操作 如果没有加锁成功，进入acquire方法
     * 2、acquire 会调用tryAcquire方法中 nonfairTryAcquire方法 判断getState()是不是0 是就再次加锁  判断是否是独占锁 是的话就加锁
     * 3、如果还是没加锁成功就返回false  执行acquireQueued()把线程加入到队列中 排队等待unlock唤醒
     * <p>
     * 不管是公平锁还是非公平锁 一旦进入队列后 都是只能 排队等待唤醒
     */
    static ReentrantLock lock = new ReentrantLock(true);

    @Override
    public String call() throws Exception {
        int sum = 0;
        while (true) {
            try {
                lock.lock();
                if (ticket > 0) {
                    //模拟卖票操作 每次票数减一
                    Thread.sleep(1);
                    ticket--;
                    sum++;
                } else {
                    return Thread.currentThread().getName() + "卖出：" + sum;
                }
            } finally {
                lock.unlock();
            }
        }
    }
}
